package no.sachmuld.methodcombination.api;

import org.junit.Assert;
import org.junit.Test;

/**
 *
 */
public abstract class DesignByContractTestTemplate {

    @Test
    public void shouldReturnValueFromMostSpecificPrimaryMethodWhenContractIsObeyed() throws Exception {

        Foo foo = createFoo();

        foo.with(new FooBehaviour().maxInput(2).maxOutput(8).returns(6));
        foo.with(new FooBehaviour().maxInput(3).maxOutput(5).returns(4));

        int expected = 4;
        int actual = foo.bar(1);

        Assert.assertEquals(expected, actual);

    }

    @Test
    public void shouldSucceedWhenAtLeastOnePreconditionIsValid() throws Exception {
        int maxInput = 2;

        Foo foo = createFoo();

        foo.with(new FooBehaviour().maxInput(maxInput).maxOutput(8).returns(6));
        foo.with(new FooBehaviour().maxInput(maxInput + 1).maxOutput(8).returns(6));

        AssertionError assertionError = null;
        try {
            foo.bar(maxInput + 1);
        } catch (AssertionError e) {
            assertionError = e;
        }
        Assert.assertTrue(assertionError == null);

    }

    @Test
    public void shouldFailWhenAllPreconditionsAreInvalid() throws Exception {
        int maxInput = 2;

        Foo foo = createFoo();

        foo.with(new FooBehaviour().maxInput(maxInput).maxOutput(8).returns(6));
        foo.with(new FooBehaviour().maxInput(maxInput + 1).maxOutput(8).returns(6));

        AssertionError assertionError = null;
        try {
            foo.bar(maxInput + 2);
        } catch (AssertionError e) {
            assertionError = e;
        }
        Assert.assertTrue(assertionError != null);

    }

    @Test
    public void shouldSucceedWhenAllPostconditionsAreValid() throws Exception {
        int maxInput = 2;
        int maxOutput = 8;

        Foo foo = createFoo();

        foo.with(new FooBehaviour().maxInput(maxInput).maxOutput(maxOutput).returns(maxOutput - 1));
        foo.with(new FooBehaviour().maxInput(maxInput).maxOutput(maxOutput - 1).returns(maxOutput - 1));

        AssertionError assertionError = null;
        try {
            foo.bar(maxInput);
        } catch (AssertionError e) {
            assertionError = e;
        }
        Assert.assertTrue(assertionError == null);

    }

    @Test
    public void shouldFailWhenAtLeastOnePostconditionIsInvalid() throws Exception {
        int maxInput = 2;
        int maxOutput = 8;

        Foo foo = createFoo();

        foo.with(new FooBehaviour().maxInput(maxInput).maxOutput(maxOutput).returns(maxOutput));
        foo.with(new FooBehaviour().maxInput(maxInput).maxOutput(maxOutput + 1).returns(maxOutput + 1));

        AssertionError assertionError = null;
        try {
            foo.bar(maxInput);
        } catch (AssertionError e) {
            assertionError = e;
        }
        Assert.assertTrue(assertionError != null);

    }

    @Test
    public void shouldAcceptMissingConditionMethods() throws Exception {
        int expected = 1;

        FooWithNoConditionMethod foo = createFooWithNoConditionMethod();
        foo.with(new FooBehaviourWithNoConditionMethod() {
            @Override
            public int bar(int i, MethodInvocation<Integer> methodInvocation) {
                return 1;
            }
        });

        int actual = foo.bar(0);
        Assert.assertEquals(expected, actual);


    }

    static public class FooBehaviour {

        private int maxInput;
        private int maxOutput;
        private int result;

        public FooBehaviour maxInput(int maxInput) {
            this.maxInput = maxInput;
            return this;
        }

        public FooBehaviour maxOutput(int maxOutput) {
            this.maxOutput = maxOutput;
            return this;
        }

        public FooBehaviour returns(int result) {
            this.result = result;
            return this;
        }


        public Condition beforeBar(int input, Context context) {
            return new Condition(input <= maxInput);
        }

        public int bar(int i, MethodInvocation<Integer> methodInvocation) {
            return result;
        }

        public Condition afterBar(int input, int result, Context context) {
            return new Condition(result <= maxOutput);
        }
    }

    static public abstract class Foo extends MethodCombinationSupport<FooBehaviour> {
        public abstract int bar(int i);
    }

    static public abstract class FooBehaviourWithNoConditionMethod {
        public int bar(int i, MethodInvocation<Integer> methodInvocation) {
            return methodInvocation.proceed();
        }
    }

    static public abstract class FooWithNoConditionMethod extends MethodCombinationSupport<FooBehaviourWithNoConditionMethod> {
        public abstract int bar(int i);
    }

    protected abstract Foo createFoo();
    protected abstract FooWithNoConditionMethod createFooWithNoConditionMethod();

}
